using System;
using System.Runtime.InteropServices;
using System.Text;
using Framework.Interop;

namespace Framework
{
    internal class ContextMenuImpl : IContextMenu
    {
        private readonly ShellMenuItem[] _menuItems;

        internal ContextMenuImpl(IFolderObject root, params IFolderObject[] fos)
        {
            if (fos == null)
            {
                throw new ArgumentNullException("fos");
            }
            if (fos.Length == 0)
            {
                throw new ArgumentOutOfRangeException("fos.Length", fos.Length, "must be >= 1");
            }
            if (root == null)
            {
                throw new InvalidOperationException();
            }

            var mis = fos.Length == 1 ? fos[0].MenuItems : root.GetMenuItems(fos);

            this._menuItems = mis ?? new ShellMenuItem[] {};
        }

        int IContextMenu.QueryContextMenu(IntPtr hmenu, uint indexMenu, uint idCmdFirst, uint idCmdLast,
                                          ContextMenuOptions uFlags)
        {
            bool noDefault = (uFlags & ContextMenuOptions.NoDefault) == ContextMenuOptions.NoDefault;

            var m = new MenuOrderEnumerator(this._menuItems, uFlags);

            while (m.MoveNext())
            {
                ((ShellMenuItem)m.Current).CreateMenuItem(hmenu, indexMenu++, (int)idCmdFirst + m.CurrentId,
                                                          m.CurrentIsDefault & !noDefault);
            }

            return this._menuItems.Length;
        }

        void IContextMenu.InvokeCommand(ref CommandInfo lpici)
        {
            this._menuItems[GetCommandId(lpici)].PerformClick(Win32Window.Create(lpici.hwnd), lpici);
        }

        void IContextMenu.GetCommandString(int idCmd, CommandStringOptions uFlags, IntPtr pwReserved, byte[] pszName,
                                           uint cchMax)
        {
            ShellMenuItem menuItem = this._menuItems[idCmd];
            string text;

            switch (uFlags)
            {
                case CommandStringOptions.HelpTextA:
                case CommandStringOptions.HelpTextW:
                    text = menuItem.HelpText;
                    break;

                case CommandStringOptions.VerbA:
                case CommandStringOptions.VerbW:
                    text = menuItem.Verb;
                    break;

                case CommandStringOptions.ValidateA:
                case CommandStringOptions.ValidateW:
                    if (idCmd < 0 || idCmd >= this._menuItems.Length)
                    {
                        Marshal.ThrowExceptionForHR(1); // S_FALSE
                    }
                    throw new Exception(); // unreachable

                default:
                    throw new ArgumentOutOfRangeException("uFlags", uFlags.ToString());
            }

            if (text == null)
            {
                text = string.Empty;
            }

            var buf = (uFlags & CommandStringOptions.Unicode) == CommandStringOptions.Unicode
                          ? Encoding.Unicode.GetBytes(text)
                          : Encoding.ASCII.GetBytes(text);

            int cch = Math.Min(buf.Length, pszName.Length - 1);

            if (cch > 0)
            {
                Array.Copy(buf, 0, pszName, 0, cch);
            }
            else
            {
                // null terminate the buffer
                pszName[0] = 0;
            }
        }

        private int GetCommandId(CommandInfo ci)
        {
            if (User32.IsIntResource(ci.lpVerb))
            {
                var id = (int)ci.lpVerb;

                if (id < 0 || id > this._menuItems.Length)
                {
                    throw new InvalidOperationException("ID " + id + "out of range");
                }
                return id;
            }
            string verb = Marshal.PtrToStringAnsi(ci.lpVerb);

            for (int id = 0; id < this._menuItems.Length; id++)
            {
                if (this._menuItems[id].Verb == verb)
                {
                    return id;
                }
            }

            throw new InvalidOperationException("command ID not found");
        }
    } ;
}
